package org.hismig.migration.strategy;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import org.hismig.cfg.Strategy;
import org.hismig.cfg.Table;
import org.hismig.migration.Context;
import org.hismig.migration.executor.TableExecutorBase;
import org.hismig.migration.result.ResultInfo;
import org.hismig.utils.CheckTool;
import org.hismig.utils.JDBCTool;
import org.hismig.utils.StringTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @declare: //TODO <br />
 *           This class created by Vansion.Hopihit on 2012-07-04 <br />
 *           <a href="mailto:vansionhopihit@gmail.com">Contact Me</a>
 * @version 1.0
 * @author Vansion.Hopihit
 * 
 */
public class RSMigration implements IStrategy {

	// private Strategy strategy;

	private static Logger logger = LoggerFactory.getLogger(RSMigration.class);

	private static final String KEY_DELETE_SOURCE = "delete_source";
	private static final String KEY_IDENTITY_SWITCH = "identity_switch";

	private static final String SYMBOL_IDENTITY_TABLE = "table";

	private boolean deleteSource;

	private String identitySwitch;

	@Override
	public ResultInfo execute(Context context,
			TableExecutorBase tableExecutorBase) {

		// sometimes the selectCondition is null
		if (null == tableExecutorBase.getTable().getSelectCondition()) {
			return null;
		}

		String tableName = tableExecutorBase.getTable().getName();
		ResultInfo resultInfo = new ResultInfo(
				RSMigration.class.getSimpleName());

		DataSource ds_source = tableExecutorBase.getDbExecutorBase()
				.getSourceDataSource();
		CheckTool.checkNotNull(ds_source);

		DataSource ds_dist = null;
		if (null != tableExecutorBase.getDbExecutorBase().getDynamicPartition()) {
			try {
				ds_dist = tableExecutorBase.getDbExecutorBase()
						.getDynamicPartition().getDataSource(context);
			} catch (SQLException e) {
				resultInfo.addException(e);
				return resultInfo;
			}
		} else {
			ds_dist = tableExecutorBase.getDbExecutorBase().getDistDataSource();
		}
		CheckTool.checkNotNull(ds_dist);

		int step = 0;

		String sql = getSelectSql(context, tableExecutorBase.getTable());
		resultInfo.setSql(sql);

		step++;
		logger.debug(String.format("[step-%s]Get select sql : %s",
				new Object[]{step, sql}));

		Connection conn_source = null;
		Statement stmt_source = null;
		ResultSet rs_source = null;

		Connection conn_dist = null;
		PreparedStatement pstmt_dist = null;
		Statement stmt_dist = null;
		try {
			resultInfo.setStartTime(System.currentTimeMillis());
			conn_dist = ds_dist.getConnection();

			// Get source query
			conn_source = ds_source.getConnection();
			stmt_source = conn_source.createStatement();
			rs_source = stmt_source.executeQuery(sql);

			resultInfo.setEndTime(System.currentTimeMillis());

			step++;
			logger.debug(String.format("[step-%s]Get source query (cost:%sms)",
					new Object[]{step, resultInfo.getExecuteTime()}));

			// Check and create table on dist_source
			JDBCTool.checkAndCreateTable(conn_dist, tableName, rs_source);

			step++;
			logger.debug(String
					.format("[step-%s]Check and create [table:%s] on dist_source (cost:%sms)",
							new Object[]{
									step,
									tableName,
									System.currentTimeMillis()
											- resultInfo.getEndTime()}));
			resultInfo.setEndTime(System.currentTimeMillis());

			// Get prepareStateMent insert sql
			String insertSql = JDBCTool.getPreparedInsertSql(tableName,
					rs_source.getMetaData());

			step++;
			logger.debug(String.format("[step-%s]Get insert sql : %s",
					new Object[]{step, insertSql}));

			// Prepare values
			pstmt_dist = conn_dist.prepareStatement(insertSql);
			while (rs_source.next()) {

				for (int i = 1; i <= rs_source.getMetaData().getColumnCount(); i++) {
					pstmt_dist.setObject(i, rs_source.getObject(i));
				}

				pstmt_dist.addBatch();
			}

			step++;
			logger.debug(String.format(
					"[step-%s]Prepare values ready (cost:%sms)",
					new Object[]{
							step,
							System.currentTimeMillis()
									- resultInfo.getEndTime()}));
			resultInfo.setEndTime(System.currentTimeMillis());

			// Set identity properties
			if (!StringTool.isEmpty(identitySwitch)) {
				String sql_identity = StringTool.replaceString(identitySwitch,
						SYMBOL_IDENTITY_TABLE, tableName);

				stmt_dist = conn_dist.createStatement();
				stmt_dist.executeUpdate(sql_identity);

				step++;
				logger.debug(String.format(
						"[step-%s]Set identity properties : %s", new Object[]{
								step, identitySwitch}));
				resultInfo.setEndTime(System.currentTimeMillis());
			}

			// Commit dist insert exeucteBatch
			conn_dist.setAutoCommit(false);
			int[] counts = pstmt_dist.executeBatch();

			for (int count : counts) {
				resultInfo.setEffectCount(resultInfo.getEffectCount() + count);
			}

			step++;
			logger.debug(String
					.format("[step-%s]Commit dist insert exeucteBatch successful (cost:%sms)",
							new Object[]{
									step,
									System.currentTimeMillis()
											- resultInfo.getEndTime()}));
			resultInfo.setEndTime(System.currentTimeMillis());

			// Delete source data
			if (deleteSource) {
				stmt_source.executeUpdate(getDeleteSql(context,
						tableExecutorBase.getTable()));
				step++;
				logger.debug(String.format(
						"[step-%s]Delete source data successful (cost:%sms)",
						new Object[]{
								step,
								System.currentTimeMillis()
										- resultInfo.getEndTime()}));
				resultInfo.setEndTime(System.currentTimeMillis());
			}

			// Commit the transaction
			conn_dist.commit();

			step++;
			logger.debug(String.format(
					"[step-%s]Commit the transaction successful (cost:%sms)",
					new Object[]{
							step,
							System.currentTimeMillis()
									- resultInfo.getEndTime()}));
			resultInfo.setEndTime(System.currentTimeMillis());

			logger.info(String.format(
					"Execute successful (effectCount:%s)(cost:%sms)",
					new Object[]{resultInfo.getEffectCount(),
							resultInfo.getExecuteTime()}));

		} catch (SQLException e) {
			resultInfo.addException(e);
			logger.error(e.toString(), e);

			JDBCTool.rollbackQuietly(conn_dist);
			resultInfo.setEndTime(System.currentTimeMillis());
			logger.info("Execute rollback completely");
		} finally {
			JDBCTool.closeQuietly(conn_source, stmt_source, rs_source);
			JDBCTool.closeQuietly(pstmt_dist);
			JDBCTool.closeQuietly(conn_dist);
		}

		return resultInfo;
	}
	private String getSelectSql(Context context, Table table) {
		return String.format(
				"SELECT * FROM %s %s",
				new Object[]{
						table.getName(),
						context.getParsedVariable(table.getSelectCondition()
								.getValue())});
	}

	private String getDeleteSql(Context context, Table table) {
		return String.format(
				"DELETE FROM %s %s",
				new Object[]{
						table.getName(),
						context.getParsedVariable(table.getSelectCondition()
								.getValue())});
	}

	@Override
	public void setStrategy(Strategy strategy) {
		CheckTool.checkNotNull(strategy);
		// this.strategy = strategy;

		String str_delete = strategy.getParamByName(KEY_DELETE_SOURCE);

		deleteSource = StringTool.convertToBoolean(str_delete, false);

		identitySwitch = strategy.getParamByName(KEY_IDENTITY_SWITCH);
	}

}
